package interview.i_003_concurrent;

import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 写一个固定容量同步容器，拥有 put 和 get 方法，以及 getCount 方法，能够支持生产者线程以及消费者线程的阻塞调用
 * 使用 wait 和 notify/notifyAll 来实现
 * 使用 Lock 和 Condition 来实现
 * 对比两种方式，Condition 的方式可以更加精确的指定哪些线程被唤醒
 */
public class T003_MyContainer {
    static final ExecutorService executorService = Executors.newFixedThreadPool(8);
    static final List<Object> list = new LinkedList<>();
    static final int MAX = 8; // 最多元素个数

    public static void main(String[] args) {
        for (int i = 0; i < 4; i++) { // 生产者
            // executorService.submit(T003_MyContainer::put1);
            executorService.execute(T003_MyContainer::put2);
        }
        for (int i = 0; i < 4; i++) { // 消费者
            // executorService.submit(T003_MyContainer::get1);
            executorService.execute(T003_MyContainer::get2);
        }
    }

    static void put1() {
        while (true) {
            synchronized (T003_MyContainer.class) {
                while (list.size() == MAX) { // 为什么用 while 而不是用 if ?
                    try {
                        T003_MyContainer.class.wait();
                    } catch (InterruptedException ignored) {
                    }
                }
                String uuid = UUID.randomUUID().toString();
                list.add(uuid);
                System.out.printf("%s %d producer %s%n", Thread.currentThread().getName(), list.size(), uuid);
                T003_MyContainer.class.notifyAll(); // 通知消费者线程进行消费
            }
            try {
                Thread.sleep(ThreadLocalRandom.current().nextLong(3000));
            } catch (InterruptedException ignored) {
            }
        }
    }

    static void get1() {
        while (true) {
            synchronized (T003_MyContainer.class) {
                while (list.isEmpty()) {
                    try {
                        T003_MyContainer.class.wait();
                    } catch (InterruptedException ignored) {
                    }
                }
                System.out.printf("%s %d consumer %s%n", Thread.currentThread().getName(), list.size(), list.remove(0));
                T003_MyContainer.class.notifyAll(); // 通知生产者进行生产
            }
            try {
                Thread.sleep(ThreadLocalRandom.current().nextLong(3000));
            } catch (InterruptedException ignored) {
            }
        }
    }

    static synchronized int getCount() {
        return list.size();
    }

    // =============================================================================================
    static final Lock lock = new ReentrantLock();
    static final Condition producer = lock.newCondition();
    static final Condition consumer = lock.newCondition();

    static void put2() {
        while (true) {
            try {
                lock.lock();
                while (list.size() == MAX) producer.await(); // 为什么用 while 而不是用 if ?
                String uuid = UUID.randomUUID().toString();
                list.add(uuid);
                System.out.printf("%s %d producer %s%n", Thread.currentThread().getName(), list.size(), uuid);
                consumer.signalAll(); // 通知消费者线程进行消费
            } catch (InterruptedException ignored) {
            } finally {
                lock.unlock();
            }
            try {
                Thread.sleep(ThreadLocalRandom.current().nextLong(3000));
            } catch (InterruptedException ignored) {
            }
        }
    }

    static void get2() {
        while (true) {
            try {
                lock.lock();
                while (list.isEmpty()) consumer.await();
                System.out.printf("%s %d consumer %s%n", Thread.currentThread().getName(), list.size(), list.remove(0));
                producer.signalAll(); // 通知生产者进行生产
            } catch (InterruptedException ignored) {
            } finally {
                lock.unlock();
            }
            try {
                Thread.sleep(ThreadLocalRandom.current().nextLong(3000));
            } catch (InterruptedException ignored) {
            }
        }
    }
}